[๐ฑ JPA ๋ฐฉํ์ถ ์์ฝ ๋๊ธฐ] ๋ฌ์ ๋ฏธ์ ์ ์ถํฉ๋๋ค.#573
[๐ฑ JPA ๋ฐฉํ์ถ ์์ฝ ๋๊ธฐ] ๋ฌ์ ๋ฏธ์
์ ์ถํฉ๋๋ค.#573soohyun1904 wants to merge 6 commits into
Conversation
๐ฌ 2๋จ๊ณ โ ๋ด ์์ฝ ๋ชฉ๋ก ์กฐํ (์ฟผ๋ฆฌ ๋ฉ์๋ / N+1 / fetch join vs @EntityGraph)๋ค์ด๊ฐ๊ธฐ ์ ์๊ธฐ ์ง๋จ
ํ์ฌ ํ
์ด๋ธ์ member ์์ด ์ด๋ฆ๋ง ๋ณด์ ํ์ง๋ง, Q. ๋ฉ์๋ ์ด๋ฆ ์ฟผ๋ฆฌยทJPQL ์ค ์ด๋ ๊ฒ์ ์ผ๋?๋จ๊ณ๋ณ๋ก ์ฎ๊ฒจ๊ฐ๋ฉฐ ํ์๋ค.
1) LazyInitializationException
@Query("select r from Reservation r " +
"join fetch r.member join fetch r.slot s " +
"join fetch s.theme join fetch s.time " +
"where r.member.id = :memberId")
List<Reservation> findMineWithDetails(@Param("memberId") Long memberId);ํด๊ฒฐ ํ ๋ก๊ทธ โ JOIN ํ ๋ฐฉ์ผ๋ก ํฉ์ณ์ง: select r1_0.id, ..., m1_0.id, m1_0.name, ..., t1_0.*, t2_0.*
from reservation r1_0
join member m1_0 on m1_0.id = r1_0.member_id
join slot s1_0 on s1_0.id = r1_0.slot_id
join theme t1_0 on t1_0.id = s1_0.theme_id
join reservation_time t2_0 on t2_0.id = s1_0.time_id
where m1_0.id = ?์ต์ข ์ฑํ โ @EntityGraph@EntityGraph(attributePaths = {"member", "slot", "slot.theme", "slot.time"})
List<Reservation> findByMemberId(Long memberId);๊ฒฝ๋ก ํ๊ธฐ: fetch join๊ณผ ์ฐจ์ด: ๋ฉ์๋ ์ด๋ฆ ์ฟผ๋ฆฌ์ ์น์ ์ ์๊ณ (fetch join์ JPQL ํ์), ์ฟผ๋ฆฌยท์ฑ๋ฅ์ ์ฌ์ค์ ๋์ผ. ์กฐ์ธ INNER/LEFT ๊ท์น (๊ทธ๋ํ ๊ด์ฐฐ):
Q. ๊ทธ ๊ฒฐ์ ์ ํ๊ณ๋?
ํ์ฌ ๊ธฐ์ค: member/slot/theme/time ๋ชจ๋ ํต์ฌ: ๋จ์ ์กฐํ๋ @EntityGraph, ๋ณต์กํ ์กฐ๊ฑด์ fetch join. ์ ๊ตํ ์ฟผ๋ฆฌ(์ผ๋ถ ์ปฌ๋ผยท์ง๊ณยท์ฐ๊ด๋ณ ์กฐ๊ฑด)๋ DTO ์กฐํ/QueryDSL๋ก. |
๐ฌ 3๋จ๊ณ โ ์์ฝ ๋๊ธฐ ๊ธฐ๋ฅ (๋๋ฉ์ธ ์ค๊ณ ํ๋จ / JPQL ๋ณธ๊ฒฉ)๋ค์ด๊ฐ๊ธฐ ์ ์๊ธฐ ์ง๋จ
์ฒซ ์ง๊ฐ โ status ์ปฌ๋ผ (์ ํ A) @Entity
public class Reservation {
@Enumerated(EnumType.STRING)
private Status status; // APPROVED, WAITING
}๊ทผ๊ฑฐ: ๋๊ธฐ์ ํ์ ์ ๋ณธ์ง์ ์ผ๋ก ๊ฐ์ "์์ฝ ํ์"์ด๊ณ ์น์ธ ์ฌ๋ถ๋ง ๋ค๋ฅด๋ค. ๋๊ธฐ ์น์ธ์ ๋ถ๋ฆฌ(์ ํ B)๋ ๊ณ ๋ ค โ ํธ๋ ์ด๋์คํ
ํ๋จ ๊ธฐ์ค = ์ ํ ๋น๋: ๋ฐฉํ์ถ์ ๋๊ธฐโํ์ ์ ํ์ด ์ฆ์(์ทจ์ ์ ์๋ ์น๊ฒฉ). ๋ถ๋ฆฌํ๋ฉด ์ ํ๋ง๋ค ๋ฐ์ดํฐ ์ด์ฌ โ ๋ถ๋ด. status๋ ๊ฒช์ ํ๊ณ โ status null ๋ฌธ์ : ์์ฑ ์ status ์ ์ฑ์ฐ๋ฉด null ๊ฐ๋ฅ. ์์ํ ์ ์ ๋ณด์ฅ ์ฝํจ. ์ํ ํจํด์? ์ํ 2๊ฐ(๋๊ธฐ/ํ์ )๋ฟ์ด๋ผ enum์ผ๋ก ์ถฉ๋ถ. ์ํ ํจํด์ ์ํ๊ฐ ์ฌ๋ฟ์ด๊ณ ์ ์ด๊ฐ ๋ณต์กํ ๋. ํ์ฌ๋ ๊ณผํจ. 3-1. N+1๊ณผ fetch join ๋น๊ต (๊ด์ฐฐ ๊ณผ์ 2)GET /reservations-mine์์ ์์ฝ N + ๋๊ธฐ M์ ๊ฐ์ ธ์ DTO ๋ณํ ์:
3-2. JPQL ๋ณธ๊ฒฉ โ N๋ฒ์งธ ๋๊ธฐ ๊ณ์ฐ๋ฉ์๋ ์ด๋ฆ ์ฟผ๋ฆฌ๋ก ๋ชป ํ โ JPQL + ์๋ธ์ฟผ๋ฆฌ + CASE WHEN. @Query("""
SELECT r.id,
CASE WHEN r.status = ...APPROVED THEN 0L
ELSE (SELECT COUNT(w) + 1L FROM Reservation w
WHERE w.slot = r.slot
AND w.status = ...WAITING
AND w.createdAt < r.createdAt)
END
FROM Reservation r WHERE r.member.id = :memberId
""")Q. JPQL์ด ๋ฐํํ๋ SQLselect r1_0.id,
case when r1_0.status='APPROVED' then 0
else (select (count(r2_0.id)+1) from reservation r2_0
where r2_0.slot_id=r1_0.slot_id
and r2_0.status='WAITING'
and r2_0.created_at<r1_0.created_at)
end
from reservation r1_0
where r1_0.member_id=?Q. ๋ฉ์๋ ์ด๋ฆ ์ฟผ๋ฆฌ๋ก๋ ์ ๋ชป ํธ๋? (1์ค)
|
๐ฌ 4๋จ๊ณ โ ์์ฝ ๋๊ธฐ ๊ด๋ฆฌ + ์๋ ์น์ธ (ํธ๋์ญ์ ๊ฒฝ๊ณ / ๋์์ฑ / flush ์์)๋ค์ด๊ฐ๊ธฐ ์ ์๊ธฐ ์ง๋จ
์๋ ์น์ธ์ ๋ฐ์ดํฐ ์ ํฉ์ฑ ๋ฌธ์ ๋ค. ์ทจ์/์์ ํ ์๋ ์น์ธ์ด ์ ๋๋ก ๋ฐ์ ์ ๋๋ฉด "์น์ธ์ด ๋น์ด๋ฒ๋ฆฐ ์์ฝ"์ด ์๊ธธ ์ ์๋ค. ๊ทธ๋ฌ๋ ํต์งธ๋ก ํ๋์ ๋น์ฆ๋์ค ๋ก์ง(=ํ๋์ ํธ๋์ญ์ )์ผ๋ก ๋ฌถ์ด์ผ ํ๋ค.
ํธ๋์ญ์ ์ ์์์ฑ์ด ํ์ํ ๋ฒ์๊น์ง ๋ฌถ๋๋ค โ ํ๋๋ผ๋ ์คํจํ๋ฉด ์ ๋ถ ๋กค๋ฐฑ๋ผ์ผ ํ๋ ์์ ๋ค๊น์ง. ๋ฐ์ดํฐ ์ ํฉ์ฑ์ด ๊ฑธ๋ฆฐ ๊ฑด(์ํ ์ ์ด) ๋ฌถ๊ณ , ๋๋๋ฆด ํ์ ์๋ ๊ฑด(์๋ฆผยท๋ก๊ทธยท์ธ๋ถ ํธ์ถ) ํธ๋์ญ์ ๋ฐ์ผ๋ก ๋บ๋ค. ํธ๋์ญ์ ์ ์งง๊ฒ ์ ์ง. Q. ์๋ ์น์ธ ๋ก์ง์ ์์น๋?์์ ยท์ญ์ ์์ ๋ณ๊ฒฝ(์ทจ์ ๋ฑ)์ด ์ผ์ด๋๋ฉด, ๊ฐ์ ํ๋ฆ์์ ์๋ ์น์ธ์ด ์ผ์ด๋๋ค. (๋ณ๋ ์ด๋ฒคํธ๋ก ๋ถ๋ฆฌํ์ง ์๊ณ Service ๋ฉ์๋ ๋ด ์ฒ๋ฆฌ)
๋ณธ์ง ์ ํธ ์ ๊ฒโ ํธ๋์ญ์ ๊ฒฝ๊ณ โ ํ ๋ฉ์๋ vs ๋ถ๋ฆฌ
Q. ๊ทธ ๊ฒฐ์ ์ ํ๊ณ โ ํธ๋์ญ์ ๊ฒฝ๊ณยท๋์์ฑยท์ผ๊ด์ฑ ์ค ๊ฐ์ฅ ์ฝํ ๊ฒ์?๋์์ฑ์ด ๊ฐ์ฅ ์ฝํ๋ค.
๋์์ฑ ๋ณด์ โ ๋ฝ ์ ํ (์ฐธ๊ณ )
์๋ ์น์ธ์ "ํ ์๋ฆฌ 2๋ช "์ด ์น๋ช ์ ์ด๋ผ ํ์คํ ์์ฐจ ์ฒ๋ฆฌ ํ์ โ ์ฌ๋กฏ ๋น๊ด์ ๋ฝ์ด ์ ํฉ. |
[JPA ์ ํ] 1๋จ๊ณ โ ๋งคํ ๋ณํ ยท ์ฐ๊ด๊ด๊ณ ยท ์์์ฑ ์ปจํ ์คํธ ๊ด์ฐฐ
์์ฝ
JdbcTemplate ๊ธฐ๋ฐ ์์์ฑ ๊ณ์ธต์ JPA๋ก ์ ํํ๋ ๋ฏธ์ ์ 1๋จ๊ณ๊น์ง ์งํํ์ต๋๋ค. ๋๋ฉ์ธ ๋ก์ง(Service/๋๋ฉ์ธ ๊ฐ์ฒด)์ ๊ฑฐ์ ๊ฑด๋๋ฆฌ์ง ์๊ณ , ์์์ฑ ๊ณ์ธต ๊ต์ฒด์ ์ํฐํฐ ๋งคํ์ ์ง์คํ์ต๋๋ค.
1. ๋จ๊ณ๋ณ ๋๋ฌ ์ง์
1-1. ๋งคํ ๋ณํ
build.gradle:spring-boot-starter-data-jpa์ถ๊ฐ (๊ธฐ์กดspring-boot-starter-jdbc๋ณ์กด)Theme,ReservationTime@Entity/@Id/@GeneratedValue(IDENTITY)๋ถ์ฌ@Embeddable๋ก:ThemeName,ThumbnailUrl,ReservationName,ReservationDate@Enumerated(EnumType.STRING)์ผ๋ก (Status: APPROVED / WAITING)created_at์ DB ์๋ ์ฑ์:@Column(insertable=false, columnDefinition=...)JpaRepository<T, Long>์ธํฐํ์ด์ค๋ก ๊ต์ฒดfindFamous์ง๊ณ ์ฟผ๋ฆฌ๋@QueryJPQL๋ก ์ด๊ด ์๋ฃ1-2. ์ฐ๊ด๊ด๊ณ ๋งคํ (๋จ๋ฐฉํฅ + ๋ถ๋ถ ์๋ฐฉํฅ)
Reservation โ Slot:@ManyToOne(optional=false)+@JoinColumn("slot_id")(์ค๋ ์ชฝ)Slot โ ReservationTime:@ManyToOne(optional=false)+@JoinColumn("time_id")Slot โ Theme:@ManyToOne(optional=false)+@JoinColumn("theme_id")Slot โ Reservation:@OneToMany(mappedBy="slot")โ ๊ฑฐ์ธ ์ชฝ, ์๋ฐฉํฅ ์ ์งcascade/orphanRemoval๋ฏธ์ ์ฉ (์์ฝ์ ๋ ๋ฆฝ ๊ธฐ๋ก, ์ญ์ ๋existsBy๋ก ์ฐจ๋จ)Rank๋@Transient๋ก ๋น์์ ์ฒ๋ฆฌ (๋ฐํ์ ๊ณ์ฐ ๊ฐ)1-3. ์์์ฑ ์ปจํ ์คํธ ๊ด์ฐฐ
๊ด์ฐฐ ์ ์ฉ ํ ์คํธ 3์ข ์ผ๋ก ํ์ ํ์ธ:
@ManyToOne๊ธฐ๋ณธ๊ฐ EAGER vs@OneToMany๊ธฐ๋ณธ๊ฐ LAZY,em.find()vs JPQL์ SQL ์ฐจ์ดslot.getReservations().add())๋ง ์์ ์ FK ๋ฏธ๋ฐ์, ์ค๋ ์ชฝ(reservation.slot) ์์ ์ ์ ์ ๋ฐ์๋จ์ ๋จ๊ณ โฌ
update๋ก์ง์ ๋ณ๊ฒฝ ๊ฐ์ง(dirty checking) ๋ฐฉ์ ์์ ์ ํ2. ๋ฐํ SQL ๋ฐ์ท
INSERT โ JPA vs ๊ธฐ์กด JdbcTemplate
4. ๋ง์ค์ธ ๊ฒฐ์
๊ฒฐ์ 1 โ ์๋ฐฉํฅ ์ ์ง ์ฌ๋ถ
Slot์@OneToMany(mappedBy="slot")๋ฅผ ์ ์ฉํด ์๋ฐฉํฅ ๊ตฌ์ฑ.slot.getReservations().add())๋ง ์์ ํ๋ฉด FK๊ฐ DB์ ๋ฐ์๋์ง ์์, (2) ์ํฐํฐ ์ง๋ ฌํ ์ ์ํ์ฐธ์กฐ ์ํ.SlotReservationSyncTest๋ก ์ค๋/๊ฑฐ์ธ ๋๊ธฐํ ๊ท์น์ ๋ช ์์ ์ผ๋ก ๊ฒ์ฆํ๊ณ ,slot.getReservations()ํ์ฉ ๊ฐ๋ฅ์ฑ์ ๋จ๊ฒจ๋ .cascade๋ ์์ฝ์ ๋ ๋ฆฝ ๊ธฐ๋ก์ด๋ฏ๋ก ๋ฏธ์ ์ฉ.)5. ํ๋ค๋ฆฐ ํ ์ฅ๋ฉด
OSIV(
open-in-view) ์ค์ ๋๋ฌธ์ ํ์ฐธ ํค๋งธ์.open-in-view๊ฐ ๊ธฐ๋ณธtrue๋ผ ์ธ์ ์ด JSON ๋ณํ๊น์ง ์ด๋ ค ์์ด์์์.spring.jpa.open-in-view=false๋ก ์ค์ ํด ๋นํ์ฑํ ์๋ฃ. (ํค ์ ๋์ฌ๋ฅผ ๋น ๋จ๋ฆฌ๋ ์ค์๋ ๊ฒฝํํจ โ ์ค์ ์ด ์กฐ์ฉํ ๋ฌด์๋๋ ๊ฒ ํ์ธ.)ํ์ธ ๊ณผ์ ๋ต๋ณ
Q1. ์์ฝ ์์ฑ INSERT SQL์ด ๊ธฐ์กด(JdbcTemplate)๊ณผ ์ด๋ป๊ฒ ๊ฐ๊ณ ๋ค๋ฅธ๊ฐ? โ "2. ๋ฐํ SQL ๋ฐ์ท" ์ฐธ๊ณ . ๊ฐ์ ์ : ๋ ๋ค reservation์ INSERT. ๋ค๋ฅธ ์ : id ์ฒ๋ฆฌ ๋ฐฉ์, SQL ์์ฑ ์ฃผ์ฒด, ๋ฐ์ธ๋ฉ/์คํ ์์ .
Q2.
findById(reservationId).getSlot().getTime().getStartAt()์ด ๋ฐํํ๋ SQL? โ Reservation ๋ก๋ ์ slot_id FK๋ง ์๊ณ ,getSlot()์@ManyToOne๊ธฐ๋ณธ EAGER์ด๋ฏ๋ก JOIN์ผ๋ก ํ ๋ฐฉ์ ๋ก๋. ์ดํgetTime()๋ Slot ๋ก๋ ์ ํจ๊ป ๋ก๋ฉ๋จ. (๋จ, fetch๋ฅผ LAZY๋ก ๋ช ์ํ๋ฉดgetSlot()์ ๊ทผ ์์ ์ ๋ฐ๋ก ์กฐํ๋จ โ ํ์ฌ ๋งคํ ๊ธฐ์ค์ผ๋ก ๋ตํจ.)๋จ์ ์์ (๋ค์ PR)
update๋ก์ง ๋ณ๊ฒฝ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ์์ ์ ํ